use alloc::sync::Arc;
use std::{fmt, sync::Mutex, thread::JoinHandle};

use azul_core::{
    callbacks::{Dummy, Update},
    refany::RefAny,
    resources::{AppConfig, ImageCache, ImageRef},
    task::TimerId,
    window::MonitorVec,
};
use azul_css::{impl_option, impl_option_inner, AzString};
use azul_layout::{timer::Timer, window_state::WindowCreateOptions};
use rust_fontconfig::FcFontCache;

#[derive(Debug, Clone)]
#[repr(C)]
pub struct AzAppPtr {
    pub ptr: Box<Arc<Mutex<App>>>,
    pub run_destructor: bool,
}

impl Drop for AzAppPtr {
    fn drop(&mut self) {
        self.run_destructor = false;
    }
}

impl AzAppPtr {
    pub fn new(initial_data: RefAny, app_config: AppConfig) -> Self {
        Self {
            ptr: Box::new(Arc::new(Mutex::new(App::new(initial_data, app_config)))),
            run_destructor: true,
        }
    }

    pub fn add_window(&mut self, create_options: WindowCreateOptions) {
        if let Ok(mut l) = (&*self.ptr).try_lock() {
            l.add_window(create_options);
        }
    }

    pub fn get_monitors(&self) -> MonitorVec {
        self.ptr
            .lock()
            .map(|m| m.get_monitors())
            .unwrap_or(MonitorVec::from_const_slice(&[]))
    }

    pub fn run(&self, root_window: WindowCreateOptions) {
        if let Ok(mut l) = self.ptr.try_lock() {
            let mut app = App::new(RefAny::new(Dummy { _dummy: 0 }), l.config.clone());
            core::mem::swap(&mut *l, &mut app);
            app.run(root_window)
        }
    }
}

/// Graphical application that maintains some kind of application state
#[derive(Debug)]
pub struct App {
    /// Your data (the global struct which all callbacks will have access to)
    pub data: RefAny,
    /// Application configuration, whether to enable logging, etc.
    pub config: AppConfig,
    /// The window create options (only set at startup), get moved into the `.run_inner()` method
    /// No window is actually shown until the `.run_inner()` method is called.
    pub windows: Vec<WindowCreateOptions>,
    /// Font configuration cache (shared across all windows)
    pub fc_cache: std::sync::Arc<FcFontCache>,
}

impl App {
    #[allow(unused_variables)]
    /// Creates a new, empty application using a specified callback.
    ///
    /// This does not open any windows, but it starts the event loop
    /// to the display server
    pub fn new(initial_data: RefAny, app_config: AppConfig) -> Self {
        eprintln!("[App::new] Starting App creation");

        #[cfg(not(miri))]
        let fc_cache = {
            eprintln!("[App::new] Building FcFontCache...");
            let cache = std::sync::Arc::new(FcFontCache::build());
            eprintln!("[App::new] FcFontCache built successfully");
            cache
        };
        #[cfg(miri)]
        let fc_cache = std::sync::Arc::new(FcFontCache::default());

        eprintln!("[App::new] Setting up logging...");

        #[cfg(all(
            feature = "logging",
            feature = "use_fern_logger",
            not(feature = "use_pyo3_logger")
        ))]
        {
            crate::logging::set_up_logging(translate_log_level(app_config.log_level));
        }

        #[cfg(feature = "logging")]
        {
            if app_config.enable_logging_on_panic {
                crate::desktop::logging::set_up_panic_hooks();
            }

            if app_config.enable_visual_panic_hook {
                use std::sync::atomic::Ordering;
                crate::desktop::logging::SHOULD_ENABLE_PANIC_HOOK.store(true, Ordering::SeqCst);
            }
        }

        eprintln!("[App::new] App created successfully");

        Self {
            windows: Vec::new(),
            data: initial_data,
            config: app_config,
            fc_cache,
        }
    }

    /// Spawn a new window on the screen. Note that this should only be used to
    /// create extra windows, the default window will be the window submitted to
    /// the `.run` method.
    pub fn add_window(&mut self, create_options: WindowCreateOptions) {
        self.windows.push(create_options);
    }

    /// Returns a list of monitors available on the system
    pub fn get_monitors(&self) -> MonitorVec {
        #[cfg(target_os = "linux")]
        {
            crate::desktop::shell2::linux::get_monitors()
        }

        #[cfg(not(target_os = "linux"))]
        {
            // TODO: Implement for Windows and macOS
            MonitorVec::from_const_slice(&[])
        }
    }

    /// Start the rendering loop for the currently added windows. The run() function
    /// takes one `WindowCreateOptions` as an argument, which is the "root" window, i.e.
    /// the main application window.
    #[cfg(feature = "std")]
    pub fn run(mut self, root_window: WindowCreateOptions) {
        // Use shell2 for new implementation
        let err = crate::desktop::shell2::run(
            self.data,
            self.config.clone(),
            self.fc_cache.clone(),
            root_window,
        );

        if let Err(e) = err {
            crate::desktop::dialogs::msg_box(&format!("Error: {:?}", e));
            eprintln!("Application error: {:?}", e);
        }
    }
}

#[cfg(all(feature = "use_fern_logger", not(feature = "use_pyo3_logger")))]
const fn translate_log_level(log_level: AppLogLevel) -> log::LevelFilter {
    match log_level {
        AppLogLevel::Off => log::LevelFilter::Off,
        AppLogLevel::Error => log::LevelFilter::Error,
        AppLogLevel::Warn => log::LevelFilter::Warn,
        AppLogLevel::Info => log::LevelFilter::Info,
        AppLogLevel::Debug => log::LevelFilter::Debug,
        AppLogLevel::Trace => log::LevelFilter::Trace,
    }
}
